import { h, VNode, App } from 'vue'
import type { Component, ComponentPublicInstance } from 'vue'
import { CustomEnum, customMap, CustomMap } from 'custom/index'

export default class Custom {
  // vue app 组册使用
  public static app: App
  public static install(app: App) {
    this.app = app
  }
  public static getGlobeComponent(name: string) {
    return this.app.component(name)
  }
  // 组件信息
  public static name: CustomEnum = 'Custom';
  protected static template: Component = { render() { return h('div', this.$slots.default) } } // 使用的组件
  protected static props: any = {} // 组件需要满足的props
  protected static emits: any = {} // 组件可以触发的事件
  protected static expose: any = [] // 组件需要暴露的方法
  public static setTemplate(component: Component) {
    // 组册组件
    this.template = component
  }

  // 实例方法
  public componentName: string; //  组件名称
  public name: string; // 名称
  protected positon = 'default' // 位置，对应slot
  protected parent?: InstanceType<typeof Custom>; // 上级组件
  protected tag: Component | string; // 使用组件
  protected _defineProps: any = {}; // 默认的props定义
  protected _defineEmits: any = {}; // 默认emits，key对应emit事件，value对应expose事件
  public _props: any = {}; // 默认的props静态取值
  public _events: any = {}; // 默认事件静态取值
  protected children: Map<string, Map<string, InstanceType<typeof Custom>>> = new Map()
  protected slots: { [key in CustomEnum]?: string } = {} // 写在static里面找不到类型
  public instance?: ComponentPublicInstance;
  constructor(name: string) {
    this.name = name
    this.componentName = new.target.name
    this.tag = new.target.template
    this._defineProps = new.target.props
    this._defineEmits = new.target.emits
  }

  // 设置组件，这里一般用通用的，但是也可以使用特殊定制组件
  setComponent(component: Component | string) {
    this.tag = component
    return this
  }

  // 快速创建组件
  protected create<T extends CustomEnum>(name: T, cName: string = name): InstanceType<CustomMap[T]> {
    const map = customMap as CustomMap
    const C = map[name]
    if (!C) {
      throw new Error(`[custom error]; 模块 ${name} 不存在`)
    }
    return new C(cName) as InstanceType<CustomMap[T]>
  }

  // 构建依赖关系
  add<T extends CustomEnum>(name: T, cName: string = name): InstanceType<CustomMap[T]> {
    const position = this.slots[name] || 'default'
    return this.addChild(name, position, cName)
  }
  addChild<T extends InstanceType<typeof Custom>>(custom: T, positon?: string, cName?: string): T;
  addChild<T extends CustomEnum>(custom: T, positon?: string, cName?: string): InstanceType<CustomMap[T]>;
  addChild(custom: InstanceType<typeof Custom> | CustomEnum, positon = 'default', cName?: string): InstanceType<typeof Custom> {
    const c = typeof custom !== 'string' ? custom : this.create(custom, cName)
    return this.addInstance(c as InstanceType<typeof Custom>, positon)
  }
  protected insert(custom: Custom) {
    this.parent = custom
  }
  protected setPositon(positon = 'default') {
    this.positon = positon
  }
  private addInstance<T extends Custom>(custom: T, positon = 'default'): T {
    if (custom.parent) {
      for (let map of custom.parent.children.values()) {
        if (map.has(custom.name)) {
          map.delete(custom.name)
          break
        }
      }
    }

    if (!this.children.has(positon)) {
      this.children.set(positon, new Map())
    }
    const map = this.children.get(positon) as Map<string, Custom>
    map.set(custom.name, custom)
    custom.insert(this)
    custom.setPositon(positon)
    return custom
  }
  // 切换到上级某个组件
  upto<T extends CustomEnum>(name: T): InstanceType<CustomMap[T]> {
    const parents = new Set()
    let parent: InstanceType<typeof Custom> | undefined = this
    while (parent && !parents.has(this) && parent.componentName !== name) {
      parent = parent.parent
    }
    return parent as InstanceType<CustomMap[T]>
  }
  // 查找某个组件
  get<T extends CustomEnum>(name: T): InstanceType<CustomMap[T]> | null {
    const children = []
    const addChildren = (maps) => {
      for (let map of maps.values()) {
        children.push(map)
      }
    }
    addChildren(this.children)
    for (let i = 0; i < children.length; i++) {
      const map = children[i]
      for (let child of map.values()) {
        if (child.componentName === name) return child
        if (child.children) addChildren(child.children)
      }
    }
    return null
  }
  // 查找所有组件
  getAll<T extends CustomEnum>(name: T): Array<InstanceType<CustomMap[T]>> {
    const children = []
    const addChildren = (maps) => {
      for (let map of maps.values()) {
        children.push(map)
      }
    }
    const result = []
    addChildren(this.children)
    for (let i = 0; i < children.length; i++) {
      const map = children[i]
      for (let child of map.values()) {
        if (child.componentName === name) {
          result.push(child)
        }
        if (child.children) addChildren(child.children)
      }
    }
    return result
  }
  // 给某个组件发送事件
  dispatch(target: CustomEnum, event: string, ...rest: any[]) {
    const t = this.upto(target)
    if (!t) return
    const compRef = t.instance?.$refs.custom || {}
    const exporseEvent: any = compRef[event]
    exporseEvent && exporseEvent(...rest)
  }

  // 这里还要再用get来包裹一层，为了后面扩展
  protected get defineProps() {
    return this._defineProps
  }
  // 设置默认prop属性，用来继承的时候覆盖
  protected get props() {
    return this._props;
  }

  // 触发事件 用get来包裹一层，为了后面扩展
  protected get defineEmits() {
    return this._defineEmits
  }
  protected get events() {
    return this._events
  }

  // 使用中间过程
  protected useAttrs(props?: any, ctx?: any) {
    return {}
  }

  private _component: Component;
  // 获取组件
  getComponent(): Component {
    if (this._component) return this._component
    const custom = this
    const getChildren = () => {
      const children: any = {}
      custom.children.forEach((map, key) => {
        children[key] = (slotProps: any) => {
          const nodes: VNode[] = []
          map.forEach(item => {
            const node = h(
              item.getComponent(),
              {
                ...slotProps
              }
            )
            nodes.push(node)
          })
          return nodes
        }
      })
      return children
    }
    const component = {
      name: this.name,
      props: custom.defineProps,

      created() {
        custom.instance = this
      },
      setup(props, ctx) {
        const tag = typeof custom.tag === 'string' ? Custom.getGlobeComponent(custom.tag) : custom.tag
        
        const { attrs } = ctx
        // 生成props
        const staticProps = custom.props
        // 生成emits
        const staticEvents = custom.events
        const emitEvents: any = {}
        for(let key in custom.defineEmits) {
          emitEvents[`on${key.replace(/[a-z]/, _ => _.toLocaleUpperCase())}`] = (...rest: any[]) => {
            const target: any = custom.parent?.instance?.$refs.custom
            if (!target) return
            const event = custom.defineEmits[key]
            target[event] && target[event](...rest)
          }
        }

        return () => h(
          tag || custom.tag,
          {
            ...attrs,
            ...props,
            ...staticProps,
            ...emitEvents,
            ...staticEvents,
            ...custom.useAttrs(props, ctx),
            ref: 'custom'
          },
          getChildren()
        )
      }
    }

    this._component = component
    return component
  }
}
